/*
 *  This is a very small gcc plugin to generate a function callgraph of the compiled source
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  These are the four essential freedoms with GNU GPL software:
 *  1: freedom to run the program, for any purpose
 *  2: freedom to study how the program works, and change it to make it do what you wish
 *  3: freedom to redistribute copies to help your Free Software friends
 *  4: freedom to distribute copies of your modified versions to your Free Software friends
 *   ,           ,
 *  /             \
 * ((__-^^-,-^^-__))
 * `-_---'  `---_-'
 *  `--|o`   'o|--'
 *      \  `  /
 *       ): :(
 *       :o_o:
 *        "-"
 */

#include "gcc-plugin.h"
#include "plugin.h"
#include "plugin-version.h"

#include "tree.h"
#include "cgraph.h"

#include <string>
#include <vector>
#include <map>

#define DEBUG(...) /**/
/* #define DEBUG(...) fprintf(stderr, __VA_ARGS__) */

int plugin_is_GPL_compatible;

extern symbol_table *symtab;

/* Define main structure. */
typedef std::string Name;
typedef std::vector<std::string> Callees;

typedef std::map<Name, Callees> CallGraph;

static CallGraph call_graph;

#define FUNCTION_NAME(fun) (const char*) IDENTIFIER_POINTER \
  (DECL_ASSEMBLER_NAME ((fun) ->decl))

/* Plugin callback function for PLUGIN_ALL_IPA_PASSES_START */

void
dump_callgraph (void * ARG_UNUSED (gcc_data),
                void * ARG_UNUSED (data))
{
    FILE *f = NULL;
    const char *func = NULL;
    size_t namelen = 0;
    size_t extlen = 0;
    char *buf = NULL;
    int n = 0;
    const char *dump_base_name2 = NULL;

  for (cgraph_node* node = symtab->first_function ();
       node;
       node = symtab->next_function (node))
    {
      if (!node->callees)
        continue;

      for (cgraph_edge* edge = node->callees;
           edge;
           edge = edge->next_callee)
        {
	    if (edge->caller->get_fun()) {
        	    std::string sfn (FUNCTION_NAME (edge->caller->get_fun ()));
		    if (edge->callee->get_fun()) {
        		std::string dfn (FUNCTION_NAME (edge->callee->get_fun ()));
        		call_graph[sfn].push_back (dfn);
			n++;
		    } else {
			/* std todo. this can happen and creates segv */
		    }
    	    } else {
		/* sfn todo can be nil */
	    }
	}
    }

    if(n == 0) {
	return;
    }

  for (CallGraph::iterator iter = call_graph.begin ();
       iter != call_graph.end ();
       ++iter)
    {
	// only create file when there is data
	if (!f) {
	    if (dump_base_name) {
		dump_base_name2 = dump_base_name;
	    } else {
		dump_base_name2 = "scallgraph";
	    }
	    namelen = strlen (dump_base_name2);
	    extlen = strlen (".scallgraph.gv") + 1;
	    buf = XALLOCAVEC (char, namelen + extlen);
	    memcpy (buf, dump_base_name2, namelen);
	    memcpy (buf + namelen, ".scallgraph.gv", extlen);
	    f = fopen (buf,"w");
	    fprintf (f,"// function callgraph generated with gcc scallgraph plugin running with gcc version %s\n",gcc_version.basever);
	    fprintf (f, "digraph scallgraph {\n");
	}
        DEBUG ("%s\n", iter->first.c_str ());
	func = iter->first.c_str ();
        for (Callees::iterator citer = iter->second.begin ();
             citer != iter->second.end ();
             ++citer)
          {
            DEBUG ("\t-> %s\n", citer->c_str ());
	    fprintf (f," \"%s\" -> \"%s\";\n", func, citer->c_str () );
          }
    }

    if(f) {
	fprintf (f, "}\n");
	fclose (f);
	f = NULL;
    }

    return;
}


int
plugin_init (struct plugin_name_args *plugin_info,
             struct plugin_gcc_version *version)
{
  /* Check the building plugin compiler and the executing compiler are the
     same.  */
  if (!plugin_default_version_check (version, &gcc_version))
    return 1;


  /* Dump the callgraph before IPA passes.  */
  register_callback (plugin_info -> base_name,
                     PLUGIN_ALL_IPA_PASSES_START,
                     dump_callgraph,
                     NULL);

  return 0;
}

/* end. */
